// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package ssagen

import (
	"bytes"
	"fmt"

	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/internal/obj"
	"cmd/internal/src"
)

func EnableNoWriteBarrierRecCheck() {
	nowritebarrierrecCheck = newNowritebarrierrecChecker()
}

func NoWriteBarrierRecCheck() {
	// 现在已知写障碍。检查
	// 调用图。
	nowritebarrierrecCheck.check()
	nowritebarrierrecCheck = nil
}

var nowritebarrierrecCheck *nowritebarrierrecChecker

type nowritebarrierrecChecker struct {
	// 在以后的分析中，extraCalls包含可能不可见的额外函数调用。它从
	// 调用者的ODCLFUNC映射到被调用者列表。
	extraCalls map[*ir.Func][]nowritebarrierrecCall

	// curfn是AST行走期间的当前函数。
	curfn *ir.Func
}

type nowritebarrierrecCall struct {
	target *ir.Func // 呼叫者或被呼叫者
	lineno src.XPos // 呼叫线路
}

// newNowritebarrierrecChecker创建一个nowritebarrierrecChecker。它
// 必须在漫游前调用
func newNowritebarrierrecChecker() *nowritebarrierrecChecker {
	c := &nowritebarrierrecChecker{
		extraCalls: make(map[*ir.Func][]nowritebarrierrecCall),
	}

	// 找到所有系统堆栈调用并记录它们的目标。在
	// 一般情况下，流分析无法查看systemstack，但在本次检查中处理它非常重要，因此我们直接对其建模。这必须发生在转换walk中的闭包之前，因为
	// 之后要解决这个问题要困难得多。
	for _, n := range typecheck.Target.Decls {
		if n.Op() != ir.ODCLFUNC {
			continue
		}
		c.curfn = n.(*ir.Func)
		if c.curfn.ABIWrapper() {
			// 我们只想要对这些
			// 函数的“真正”调用，而不是在
			// 中生成的函数自己的ABI包装器。
			continue
		}
		ir.Visit(n, c.findExtraCalls)
	}
	c.curfn = nil
	return c
}

func (c *nowritebarrierrecChecker) findExtraCalls(nn ir.Node) {
	if nn.Op() != ir.OCALLFUNC {
		return
	}
	n := nn.(*ir.CallExpr)
	if n.X == nil || n.X.Op() != ir.ONAME {
		return
	}
	fn := n.X.(*ir.Name)
	if fn.Class != ir.PFUNC || fn.Defn == nil {
		return
	}
	if !types.IsRuntimePkg(fn.Sym().Pkg) || fn.Sym().Name != "systemstack" {
		return
	}

	var callee *ir.Func
	arg := n.Args[0]
	switch arg.Op() {
	case ir.ONAME:
		arg := arg.(*ir.Name)
		callee = arg.Defn.(*ir.Func)
	case ir.OCLOSURE:
		arg := arg.(*ir.ClosureExpr)
		callee = arg.Func
	default:
		base.Fatalf("expected ONAME or OCLOSURE node, got %+v", arg)
	}
	if callee.Op() != ir.ODCLFUNC {
		base.Fatalf("expected ODCLFUNC node, got %+v", callee)
	}
	c.extraCalls[c.curfn] = append(c.extraCalls[c.curfn], nowritebarrierrecCall{callee, n.Pos()})
}

// recordCall记录了从ODCLFUNC节点“from”到函数
// symbol”到位置处的“to”的调用。
// 
// 在编译到
// 捕获精确的调用图期间，应尽可能晚地完成此操作。电话的目标是一个LSym 
// 因为这是我们启动SSA后所知道的全部。
// 
// 对于不同的节点，可以同时调用它。
func (c *nowritebarrierrecChecker) recordCall(fn *ir.Func, to *obj.LSym, pos src.XPos) {
	// 我们将此信息记录在*Func上，因此这是并发安全的。
	if fn.NWBRCalls == nil {
		fn.NWBRCalls = new([]ir.SymAndPos)
	}
	*fn.NWBRCalls = append(*fn.NWBRCalls, ir.SymAndPos{Sym: to, Pos: pos})
}

func (c *nowritebarrierrecChecker) check() {
	// 我们尽可能晚地查看调用图，这样我们就可以捕获通过降低创建的所有调用，但这意味着我们只能看到obj。一连串的电话。symToFunc让我们回到ODCLFUNCs。
	symToFunc := make(map[*obj.LSym]*ir.Func)
	// funcs记录BFS调用图的后缘。它
	// 映射每个函数的ODCLFUNC，这些函数必须没有禁止它们的调用的写障碍。直接标记为go:nowritebarrierrec的函数
	// 位于此
	// 映射中，其值为零nowritebarrierrecall。这也是
	// 作为调用图BFS的标记集。
	funcs := make(map[*ir.Func]nowritebarrierrecCall)
	// q是按BFS顺序访问的ODCLFUNC节点的队列。
	var q ir.NameQueue

	for _, n := range typecheck.Target.Decls {
		if n.Op() != ir.ODCLFUNC {
			continue
		}
		fn := n.(*ir.Func)

		symToFunc[fn.LSym] = fn

		// Make nowritebarrierrec函数BFS根。
		if fn.Pragma&ir.Nowritebarrierrec != 0 {
			funcs[fn] = nowritebarrierrecCall{}
			q.PushRight(fn.Nname)
		}
		// 检查go:nowritebarrier函数。
		if fn.Pragma&ir.Nowritebarrier != 0 && fn.WBPos.IsKnown() {
			base.ErrorfAt(fn.WBPos, "write barrier prohibited")
		}
	}

	// 从所有
	// go:nowritebarrierrec函数执行调用图的BFS。
	enqueue := func(src, target *ir.Func, pos src.XPos) {
		if target.Pragma&ir.Yeswritebarrierrec != 0 {
			// 不要流入此函数。
			return
		}
		if _, ok := funcs[target]; ok {
			// 已找到目标路径。
			return
		}

		// 记录路径。
		funcs[target] = nowritebarrierrecCall{target: src, lineno: pos}
		q.PushRight(target.Nname)
	}
	for !q.Empty() {
		fn := q.PopLeft().Func

		// 检查fn。
		if fn.WBPos.IsKnown() {
			var err bytes.Buffer
			call := funcs[fn]
			for call.target != nil {
				fmt.Fprintf(&err, "\n\t%v: called by %v", base.FmtPos(call.lineno), call.target.Nname)
				call = funcs[call.target]
			}
			base.ErrorfAt(fn.WBPos, "write barrier prohibited by caller; %v%s", fn.Nname, err.String())
			continue
		}

		// 让fn的电话排队。
		for _, callee := range c.extraCalls[fn] {
			enqueue(fn, callee.target, callee.lineno)
		}
		if fn.NWBRCalls == nil {
			continue
		}
		for _, callee := range *fn.NWBRCalls {
			target := symToFunc[callee.Sym]
			if target != nil {
				enqueue(fn, target, callee.Pos)
			}
		}
	}
}
